import * as d3 from "d3";
import { IElement } from "./useD3Heap";

const defaultNodeR = 16;
const MAX_NODE_HEIGHT = 80;
const NODE_ANIMATION_TIME = 1000;
// const MAX_LIST_LINE_SIZE = 16;

const useD3Draw = (containerID: string, containerHeight: number, containerWidth: number) => {

    let deep = 0, centerX = containerWidth / 2, currentLineHeight = containerHeight, currentNodeWidth = 0, temp;
    let mapElement: any[][][] = [];

    const drawTreeMap = (data: IElement[][]) => {
        mapElement.forEach(line => line.forEach(node => node.forEach(element => !Number.isInteger(element) && element.remove())));

        mapElement = [];
        deep = data.length;
        currentLineHeight = temp = Math.floor(containerHeight / deep);
        currentNodeWidth = Math.floor(containerWidth / Math.pow(2, deep-1));
        let linePow = Math.pow(2, deep-1), lineHalfSize = 0.5, offsetX;
        
        currentLineHeight > MAX_NODE_HEIGHT && (currentLineHeight = MAX_NODE_HEIGHT);
        // console.log(currentLineHeight, currentNodeWidth)

        data.forEach((items, lineIndex) => {
          mapElement[lineIndex] = [];
          // console.log(linePow, lineHalfSize)
          items.forEach((item, index) => {
            mapElement[lineIndex][index] = [];
            if (item !== undefined) {
              if ( lineIndex === 0 ) {
                mapElement[lineIndex][index].push(...createNode(centerX, currentLineHeight/2 , defaultNodeR, item.v));
              } else {

                mapElement[lineIndex][index].push(...createNode(
                    centerX + (currentNodeWidth * linePow) * (index - lineHalfSize + .5), 
                    currentLineHeight * (lineIndex + .5), 
                    defaultNodeR, item.v));

                // offsetX = x1 > x2? -0.3 dR : 0.3 dR
                offsetX = index % 2 === 0? -0.3*defaultNodeR : 0.3*defaultNodeR;

                mapElement[lineIndex][index].push(...createLine(
                  centerX + (currentNodeWidth * linePow*2) * (Math.floor(index/2) - lineHalfSize/2 + .5) + offsetX * (linePow*0.2 +1), 
                  currentLineHeight * (lineIndex-1 + .5)  + defaultNodeR*1.3, 
                  centerX + (currentNodeWidth * linePow) * (index - lineHalfSize + .5) - offsetX * linePow*0.8, 
                  currentLineHeight * (lineIndex + .5)  - defaultNodeR*1.3, 
                  1
                ))
              }
            }
          })
          linePow/=2;
          lineHalfSize*=2;
        });
        console.log(mapElement);

    }

    const drawListMap = (data: IElement[]) => {
        currentNodeWidth = containerWidth / data.length;
        deep = data.length;
        mapElement[0] = [];
        data.forEach((item, index) => {
            mapElement[0][index] = [];
            mapElement[0][index].push(...createNode(defaultNodeR + currentNodeWidth*index, containerHeight/2, defaultNodeR, item.v));
            if ( index !== deep-1 ) {
                mapElement[0][index].push(...createLine(
                    defaultNodeR*2.5 + currentNodeWidth * index, containerHeight/2,
                    currentNodeWidth * (index+1) - defaultNodeR*0.5, containerHeight/2, 1));
            }
        })
    }

    /**
     * 绘制圆，以点(cx, cy)为圆心绘制半径为cr的圆
     * @param v 圆内文字
     */
    const createNode = (cx: number, cy: number, cr: number, v: number) => {
      const select = d3.select('#'+ containerID);
      return [
        select.append('circle')
            .attr('cx', cx)
            .attr('cy', cy)
            .attr('r', cr)
            .attr('fill-opacity', 0.8)
            .attr('fill', '#bbb'),
        select.append('text')
            .text(v)
            .attr('x', cx - cr/2)
            .attr('y', cy + cr/4)
            .attr('fill', 'black')
            .attr('font-size', 12), cx, cy];
    }
    
    /**
     * 绘制线
     * @param direction type of line；0: 无向线，1：单向线（箭头实心），2：双向线（箭头实心）
     */
    const createLine = (x1: number, y1: number, x2: number, y2: number, direction: number = 0) => {
      const select = d3.select('#'+ containerID);
      const res: any[] = [
        select.append('line')
        .attr('x1', x1)
        .attr('y1', y1)
        .attr('x2', x2)
        .attr('y2', y2)
        .attr('stroke', 'black')
        .attr('stroke-width', '2px')
        .attr('stroke-opacity', 0.4)
      ]
      
      /*
        假设是等边三角形，高等于0.4 dR, 
      */

      let AB = Math.sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1)),
          BC = Math.sqrt(0.16/3) * defaultNodeR;

      switch(direction) {
        case 1: 
          res.push(select.append("path").attr('d', `M ${x2 + BC/AB * (y2-y1)}  ${y2 - BC/AB * (x2-x1) }
                                                    L ${x2 - BC/AB * (y2-y1)}  ${y2 + BC/AB * (x2-x1) }
                                                    L ${x2 + 0.3*defaultNodeR/AB * (x2-x1)} ${y2 + 0.3*defaultNodeR/AB * (y2-y1)} Z`))
          break;
        case 2: 
          break;
      }
      return res;
    }

    const change = (a_line: number, a_index: number, b_line: number, b_index: number) => {
      const a: any[] = mapElement[a_line][a_index];
      const b: any[] = mapElement[b_line][b_index];
      run(a, b[2], b[3]);
      run(b, a[2], a[3]);
      mapElement[b_line][b_index] = a;
      mapElement[a_line][a_index] = b;

      temp = a[4];
      a[4] = b[4];
      b[4] = temp;
      temp = a[5];
      a[5] = b[5];
      b[5] = temp;
    }

    const insertNode = (line: number, index: number, item: any) => {
      mapElement[line][index] = [];
      let linePow = Math.pow(2, deep-1 - line), lineHalfSize = Math.pow(2, line) * 0.5;

      
      mapElement[line][index].push(...createNode(
        centerX + (currentNodeWidth * linePow) * (index - lineHalfSize + .5), 
        currentLineHeight * (line + .5), 
        defaultNodeR, item.v));

      // offsetX = x1 > x2? -0.3 dR : 0.3 dR
      let offsetX = index % 2 === 0? -0.3*defaultNodeR : 0.3*defaultNodeR;

      mapElement[line][index].push(...createLine(
        centerX + (currentNodeWidth * linePow*2) * (Math.floor(index/2) - lineHalfSize/2 + .5) + offsetX * (linePow*0.2 +1), 
        currentLineHeight * (line-1 + .5)  + defaultNodeR*1.3, 
        centerX + (currentNodeWidth * linePow) * (index - lineHalfSize + .5) - offsetX * linePow*0.8, 
        currentLineHeight * (line + .5)  - defaultNodeR*1.3, 
        1
      ))

        // mapElement[line][index][0].attr('fill', '#ffe57b');
    }

    const removeAndChange = (line: number, index: number) => {
        const removeNode = mapElement[0][0];
        let lastNode = mapElement[line][index];
        lastNode[4].remove();
        lastNode[5].remove();

        run(removeNode, defaultNodeR, -defaultNodeR);
        run(lastNode, removeNode[2], removeNode[3]);
        mapElement[0][0] = lastNode;
    }

    const run = (node: any[], nextX: number, nextY: number) => {
        let start = Date.now(), now;
        node[0].attr('fill', '#ffe57b');
        let interval = setInterval(() => {
            now = Date.now() - start; 
            if ( now > NODE_ANIMATION_TIME ) {
                node[2] = nextX;
                node[3] = nextY;
                node[0].attr('cy', nextY ).attr('fill', '#bbb')
                node[1].attr('y', nextY + defaultNodeR/4 )
                node[0].attr('cx', nextX)
                node[1].attr('x', nextX - defaultNodeR/2)
                if ( nextX === defaultNodeR && nextY === -defaultNodeR ) {
                    node[0].remove();
                    node[1].remove();
                }
                clearInterval(interval);
            } else if ( now > NODE_ANIMATION_TIME / 2 ) {
                node[0].attr('cy', node[3] + (nextY - node[3]) *  ((now /  NODE_ANIMATION_TIME *2)-1) )
                node[1].attr('y', node[3] + (nextY - node[3]) *  ((now /  NODE_ANIMATION_TIME *2)-1) + defaultNodeR/4 )
            } else {
                node[0].attr('cx', node[2] + (nextX - node[2]) *  (now /  NODE_ANIMATION_TIME *2) )
                node[1].attr('x', node[2] + (nextX - node[2]) *  (now /  NODE_ANIMATION_TIME *2) - defaultNodeR/2)
            }
        }, 15);
 
    }

    return {
      drawListMap,
      drawTreeMap,
      change,
      removeAndChange,
      insertNode,
    };
}

export default useD3Draw;